package com.ib.algokit;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import org.activequant.broker.IOrderTracker;
import org.activequant.calendar.IExchangeCalendar;
import org.activequant.calendar.NASDAQCalendarSettings;
import org.activequant.core.domainmodel.InstrumentSpecification;
import org.activequant.core.domainmodel.account.Order;
import org.activequant.core.domainmodel.account.Portfolio;
import org.activequant.core.domainmodel.data.Candle;
import org.activequant.core.domainmodel.data.CandleSeries;
import org.activequant.core.domainmodel.data.Quote;
import org.activequant.core.domainmodel.data.QuoteSeries;
import org.activequant.core.domainmodel.data.TickerNews;
import org.activequant.core.domainmodel.data.TradeIndication;
import org.activequant.core.domainmodel.data.TradeIndicationSeries;
import org.activequant.core.types.OrderSide;
import org.activequant.core.types.OrderType;
import org.activequant.core.types.TimeStamp;
import org.activequant.core.types.Tuple;
import org.activequant.core.util.TimeSeriesUtils;
import org.activequant.core.util.TimeStampFormat;
import org.activequant.math.algorithms.FinancialLibrary;
import org.activequant.math.algorithms.Interval;
import org.activequant.tradesystem.types.Mode;
import org.activequant.util.charting.CandlestickChart;
import org.activequant.util.charting.Chart;
import org.activequant.util.charting.IPlotService;
import org.activequant.util.charting.TimeseriesCombinedChart;
import org.apache.log4j.Logger;
import org.jfree.chart.ChartFrame;
import org.jfree.chart.annotations.XYPointerAnnotation;
import org.jfree.ui.TextAnchor;

import com.ib.algokit.ib.CommissionsCalculator;
import com.ib.algokit.ib.IBPortfolioManagingBroker;
import com.ib.algokit.ib.IBPortfolioManagingBroker.IBPosition;

public class AnalyzerUtils implements AnalyzerPluginInterface{
	public static long WINDOW_SIZE_IN_MS=8*3600000;//4 hours in millis
	/**
	 * private final InstrumentAnalyzer instrumentAnalyzer;
	 */
	private final InstrumentAnalyzer instrumentAnalyzer;
	/**
	 * private final TraderProxy ema;
	 */
	private final TraderProxy ema;
	/**
	 * private CandleSeries allCandlesMerged;
	 */
	private CandleSeries allCandlesMerged;
	/**
	 * List&lt;Tuple&lt;TimeStamp,Double&gt;&gt; chartTuples = new ArrayList&lt;Tuple&lt;TimeStamp,Double&gt;&gt;();<br/>
	 * charts need tuples
	 */
	List<Tuple<TimeStamp,Double>> chartTuples = new ArrayList<Tuple<TimeStamp,Double>>();
	/**
	 * List&lt;Tuple&lt;TimeStamp,Double&gt;&gt; localMinimumPoints = new ArrayList&lt;Tuple&lt;TimeStamp,Double&gt;&gt;();
	 */
	List<Tuple<TimeStamp,Double>> localMinimumPoints = new ArrayList<Tuple<TimeStamp,Double>>();
	/**
	 * List&lt;Tuple&lt;TimeStamp,Double&gt;&gt; localMaximumPoints = new ArrayList&lt;Tuple&lt;TimeStamp,Double&gt;&gt;();
	 */
	List<Tuple<TimeStamp,Double>> localMaximumPoints = new ArrayList<Tuple<TimeStamp,Double>>();
	/**
	 * private Candle ONEM_T_MINUS_5_HIGH=null;<br/>
	 * Last 5 days, highest 1Minute Candle
	 */
	private Candle ONEM_T_MINUS_5_HIGH=null;
	/**
	 * private Candle ONEM_T_MINUS_5_LOW=null;<br/>
	 * Last 5 days, lowest 1Minute Candle
	 */
	private Candle ONEM_T_MINUS_5_LOW=null;
	/**
	 * private Candle ONED_T_MINUS_1Y_HIGH=null;<br/>
	 * Last 1 year, highest 1 Day Candle
	 */
	private Candle ONED_T_MINUS_1Y_HIGH=null;
	/**
	 * private Candle ONED_T_MINUS_1Y_LOW=null;<br/>
	 * Last 1 year, lowest 1 Day Candle
	 */
	private Candle ONED_T_MINUS_1Y_LOW=null;
	public final static TimeStampFormat fmt = new TimeStampFormat("yyyy/MM/dd");//("yyyy/MM/dd");
	{
		//formatter.setTimeZone(exchangeCalendar.getTimeZone());
		//formatter.setTimeZone(TimeZone.getDefault());
		fmt.setTimeZone(NASDAQCalendarSettings.getInstance().getTimeZone());
	}
	/**
	 * Constructs a new AnalyzerUtils using the given instrumentAnalyzer(InstrumentAnalyzer) to set its associated instrumentAnalyzer(InstrumentAnalyzer)
	 * @param instrumentAnalyzer
	 */
	public AnalyzerUtils(InstrumentAnalyzer instrumentAnalyzer){
		this.instrumentAnalyzer=instrumentAnalyzer;
		this.ema=instrumentAnalyzer.getEMA();
	}
	/**
	 * Finds the High/Low Candles from the historical T_MINUS_5D Candles and then sets the associated ONEM_T_MINUS_5_HIGH(Candle) and ONEM_T_MINUS_5_LOW(Candle)
	 * with those 2 Candles
	 */
	public void getTMINUS5DHighAndLowCandles(){
		CandleSeries T_MINUS_5D_HistoryCandles=instrumentAnalyzer.getT_MINUS_5D_HistoryCandles();
		ONEM_T_MINUS_5_HIGH=T_MINUS_5D_HistoryCandles.getHigh();
		ONEM_T_MINUS_5_LOW=T_MINUS_5D_HistoryCandles.getLow();
		instrumentAnalyzer.getLog().info("ONEM_T_MINUS_5_HIGH="+ONEM_T_MINUS_5_HIGH.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(ONEM_T_MINUS_5_HIGH.getTimeStamp()));
		instrumentAnalyzer.getLog().info("ONEM_T_MINUS_5_LOW="+ONEM_T_MINUS_5_LOW.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(ONEM_T_MINUS_5_LOW.getTimeStamp()));
	}
	/**
	 * Finds the High/Low Candles from the historical T_MINUS_1Y Candles and then sets the associated ONED_T_MINUS_1Y_HIGH(Candle) and ONED_T_MINUS_1Y_LOW(Candle)
	 * with those 2 Candles
	 */
	public void getTMINUS1YHighAndLowCandles(){
		CandleSeries T_MINUS_1Y_HistoryCandles=instrumentAnalyzer.getT_MINUS_1Y_HistoryCandles();
		ONED_T_MINUS_1Y_HIGH=T_MINUS_1Y_HistoryCandles.getHigh();
		ONED_T_MINUS_1Y_LOW=T_MINUS_1Y_HistoryCandles.getLow();
		instrumentAnalyzer.getLog().info("ONED_T_MINUS_1Y_HIGH="+ONED_T_MINUS_1Y_HIGH.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(ONED_T_MINUS_1Y_HIGH.getTimeStamp()));
		instrumentAnalyzer.getLog().info("ONED_T_MINUS_1Y_LOW="+ONED_T_MINUS_1Y_LOW.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(ONED_T_MINUS_1Y_LOW.getTimeStamp()));
	}
	
	/**
	 * Creates a priceTuple(Tuple&lt;TimeStamp,Double&gt;) for each of the latest maxPoints(int) T_MINUS_5D 1Min Historical Candles and adds them to the associated
	 * oneMinClosePricesLast5Days(List&lt;Tuple&lt;TimeStamp,Double&gt;&gt;)
	 * @param maxPoints
	 */
	public void initializeTMINUS5DCandles(int maxPoints){
		CandleSeries T_MINUS_5D_HistoryCandles=instrumentAnalyzer.getT_MINUS_5D_HistoryCandles();
		int size=T_MINUS_5D_HistoryCandles.size();
		for(int i=0; i < size && i<maxPoints; i++){
			Candle c = T_MINUS_5D_HistoryCandles.get(i);
			Tuple<TimeStamp, Double> priceTuple = new Tuple<TimeStamp, Double>(c.getTimeStamp(), c.getClosePrice());
			chartTuples.add(priceTuple);	
		}
	}	
	/**
	 * this resets(or not depending on the given reset(boolean)) and populates the associated chartTuples(List&lt;Tuple&lt;TimeStamp,Double&gt;&gt;) with the close prices and timestamps
	 * of the given candles(CandleSeries)
	 * @param candles
	 * @param reset
	 */
	public List<Tuple<TimeStamp,Double>> getClosePriceTuples(CandleSeries candles){
		List<Tuple<TimeStamp,Double>> tuples = new ArrayList<Tuple<TimeStamp,Double>>();
		for(Candle candle: candles){
			Tuple<TimeStamp, Double> priceTuple = new Tuple<TimeStamp, Double>(candle.getTimeStamp(), candle.getClosePrice());
			tuples.add(priceTuple);	
		}
		return tuples;
	}
	/**
	 * private TimeseriesCombinedChart timeseriesCombinedChart = null;<br/>
	 * this holds the whole chart
	 */
	private TimeseriesCombinedChart timeseriesCombinedChart = null;
	/**
	 * returns the associated timeseriesCombinedChart(TimeseriesCombinedChart)
	 * @return
	 */
	public TimeseriesCombinedChart getTimeseriesCombinedChart(){
		return timeseriesCombinedChart;
	}
	IPlotService<TimeStamp> todayPriceSeriesPlot = null;
	IPlotService<TimeStamp> todayRecomendationSeriesPlot = null;
	IPlotService<TimeStamp> todayQuantitySeriesPlot = null;
	IPlotService<TimeStamp> todayActualTradesSeriesPlot = null;
//	IPlotService<TimeStamp> todayTradeIndicationSeriesPlot = null;
	IPlotService.ISeriesService<TimeStamp> todayCandleSeriesService = null;
	IPlotService.ISeriesService<TimeStamp> todayRecomendationSeriesService = null;
	IPlotService.ISeriesService<TimeStamp> todayTradeIndicationSeriesService = null;
	IPlotService.ISeriesService<TimeStamp> todayQuoteBidSeriesService = null;
	IPlotService.ISeriesService<TimeStamp> todayQuoteAskSeriesService = null;
	
	IPlotService.ISeriesService<TimeStamp> todayQuoteBidQuantitySeriesService = null;
	IPlotService.ISeriesService<TimeStamp> todayQuoteAskQuantitySeriesService = null;
	
	IPlotService.ISeriesService<TimeStamp> todayTradeQuantitySeriesService = null;
	boolean plotQuoteBidPriceSeries=false;
	boolean plotQuoteAskPriceSeries=false;
	boolean plotTradeIndicationPriceSeries=false;
	boolean plotRecomendationSeries=false;
	boolean plotTickerNews=false;
	boolean showChart= RunMe.mode==Mode.BACKTEST;//if this is false there will be no chart at all
	
	public void onCandle(Candle liveCandle){
		if(showChart){
			if(timeseriesCombinedChart==null){
				String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
				String chartTitle=symbol+"@"+fmt.format(liveCandle.getTimeStamp());
				initTimeseriesCombinedChart(chartTitle);
			}
			todayCandleSeriesService.addDataPoint(liveCandle.getTimeStamp(), liveCandle.getClosePrice());
		}

		double noise=0.9;
//		double significantChange=0.1;
//		long lookBackPeriodNanos = 20*60*1000000000L;//20 minutes
		
//		lookBackPeriodNanos = getDailyData().get(0).getNanoSecondsToWaitOnExecution();
		double significantChange = getTargetProfit();
		
		double recommendation=getRecomendation(instrumentAnalyzer.getLiveCandleSeriesBag().get(), significantChange, noise);
		actUponRecommendation(recommendation);		
		if(todayRecomendationSeriesService!=null){	
			todayRecomendationSeriesService.addDataPoint(liveCandle.getTimeStamp(), recommendation);
		}
	}
	
	public void onTradeIndication(TradeIndication liveTradeIndication){
		if(showChart){
			if(timeseriesCombinedChart==null){
				String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
				String chartTitle=symbol+"@"+fmt.format(liveTradeIndication.getTimeStamp());
				initTimeseriesCombinedChart(chartTitle);
			}
			if(todayTradeIndicationSeriesService!=null)
			{todayTradeIndicationSeriesService.addDataPoint(liveTradeIndication.getTimeStamp(), liveTradeIndication.getPrice());}
			todayTradeQuantitySeriesService.addDataPoint(liveTradeIndication.getTimeStamp(), liveTradeIndication.getQuantity());
		}
	}
	
	public void onQuote(Quote liveQuote){
		if(showChart){
			if(timeseriesCombinedChart==null){
				String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
				String chartTitle=symbol+"@"+fmt.format(liveQuote.getTimeStamp());
				initTimeseriesCombinedChart(chartTitle);
			}
			if(todayQuoteBidSeriesService!=null){
				todayQuoteBidSeriesService.addDataPoint(liveQuote.getTimeStamp(), liveQuote.getBidPrice());
			}
			if(todayQuoteAskSeriesService!=null){
				todayQuoteAskSeriesService.addDataPoint(liveQuote.getTimeStamp(), liveQuote.getAskPrice());
			}
			todayQuoteBidQuantitySeriesService.addDataPoint(liveQuote.getTimeStamp(), liveQuote.getBidQuantity());
			todayQuoteAskQuantitySeriesService.addDataPoint(liveQuote.getTimeStamp(), -liveQuote.getAskQuantity());
		}
	}
	
	public void onTickerNews(TickerNews liveTickerNews){
		if(showChart){
			if(!plotTickerNews) return;
			if(timeseriesCombinedChart==null){
				String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
				String chartTitle=symbol+"@"+fmt.format(liveTickerNews.getTimeStamp());
				initTimeseriesCombinedChart(chartTitle);
			}
			CandleSeries liveCandleSeries= instrumentAnalyzer.getLiveCandleSeriesBag().get();
			if(liveCandleSeries==null||liveCandleSeries.size()==0){
				return;
			}
			Candle lastCandle= liveCandleSeries.get(0);
			double closePrice=lastCandle.getClosePrice();
			
	//		XYTextAnnotation annotation = new XYTextAnnotation("News", liveTickerNews.getTimeStamp().getDate().getTime(), closePrice);
	//		annotation.setToolTipText(liveTickerNews.getTitle());
	//		annotation.setRotationAngle(Math.PI/2);
			
			XYPointerAnnotation annotation = new XYPointerAnnotation("News", liveTickerNews.getTimeStamp().getDate().getTime(), closePrice, 1.570796326794897D);
			annotation.setTextAnchor(TextAnchor.TOP_CENTER);
			annotation.setToolTipText(liveTickerNews.getTitle());
			timeseriesCombinedChart.addAnnotation(annotation);
		}
	}
	
	public void initTimeseriesCombinedChart(String frameTitle){
		if(timeseriesCombinedChart==null){
			timeseriesCombinedChart = new TimeseriesCombinedChart(frameTitle, null/*"time"*/);
			timeseriesCombinedChart.setWindowSizeMillis(WINDOW_SIZE_IN_MS);
		}
		if(todayPriceSeriesPlot==null){
			todayPriceSeriesPlot=timeseriesCombinedChart.createPlot("price", 3, false, true);
		}
		if(plotRecomendationSeries&&todayRecomendationSeriesPlot==null){
			todayRecomendationSeriesPlot=timeseriesCombinedChart.createPlot("recommendation", 2, false, false);
			todayRecomendationSeriesService = todayRecomendationSeriesPlot.newSeries("reccomendation signal");
		}
		
		if(todayQuantitySeriesPlot==null){
			todayQuantitySeriesPlot=timeseriesCombinedChart.createPlot("bid/ask quantity", 2, false, false);
		}
		
		if(todayActualTradesSeriesPlot==null){
			todayActualTradesSeriesPlot=timeseriesCombinedChart.createPlot("real trades", 2, false, false);
		}
		
//		String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
		if(todayCandleSeriesService==null){
			todayCandleSeriesService = todayPriceSeriesPlot.newSeries("candle");
		}
		if(plotTradeIndicationPriceSeries&&todayTradeIndicationSeriesService==null){
			todayTradeIndicationSeriesService = todayPriceSeriesPlot.newSeries("trades");
		}
		if(plotQuoteBidPriceSeries&&todayQuoteBidSeriesService==null){
			todayQuoteBidSeriesService = todayPriceSeriesPlot.newSeries("bid");
		}
		if(plotQuoteAskPriceSeries&&todayQuoteAskSeriesService==null){
			todayQuoteAskSeriesService = todayPriceSeriesPlot.newSeries("ask");
		}
		
		if(todayQuoteBidQuantitySeriesService==null){
			todayQuoteBidQuantitySeriesService = todayQuantitySeriesPlot.newSeries("bid size");
		}
		if(todayQuoteAskQuantitySeriesService==null){
			todayQuoteAskQuantitySeriesService = todayQuantitySeriesPlot.newSeries("ask size");
		}
		
		if(todayTradeQuantitySeriesService==null){
			todayTradeQuantitySeriesService = todayActualTradesSeriesPlot.newSeries("trade size");
		}
		
//		GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 
//		GraphicsDevice[] gs = ge.getScreenDevices(); // Get size of each screen 
//		for (int i = 0; i < gs.length; i++) {
//			DisplayMode dm = gs[i].getDisplayMode();
//			int screenWidth = dm.getWidth();
//			int screenHeight = dm.getHeight();
//			timeseriesCombinedChart.setBounds(0, 0, screenWidth, screenHeight);
//		}
		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		timeseriesCombinedChart.setBounds(0, 0, (int)dim.getWidth(), (int)dim.getHeight());
		timeseriesCombinedChart.setVisible(true);
		
		for(Tuple<TimeStamp, Double> priceTuple:chartTuples){
			todayCandleSeriesService.addDataPoint(priceTuple.getObject1(), priceTuple.getObject2());
		}
	}

	
	double weightedOptimalProfit=0;
	public double getTargetProfit(){
		return 10;
	}
	
	public long getNanoSecondsToWaitOnExecution(){
		return nanoSecondsToWaitOnExecution;
	}
	
	long nanoSecondsToWaitOnExecution=0;
	/**
	 * If not set yet, loops through intra day prices of last 5 business days and for days with more than 50 intra-day prices, it calculates DayFeatures with optimalTargetProfit and timeToWaitOnExecution.<br/>
	 * Then it calculates a weighted average (last day -> biggest impact) and sets the associated weightedNanoSecondsToWaitOnExecution(long) and weightedOptimalProfit(double)
	 */

	/**
	 * Gets the exchange trading hours for the last 5 days and draws a line and candlestick chart(optionally) for each of them using the T_MINUS_5D historical 1 min candles.<br/>
	 * It also charts a line of significant points on the line charts.
	 * @param drawCandleStickChart - if true , will draw a candlestick chart for each day as well
	 * @param drawCurrentDayCandles - if true, will draw current day candles between opening and now \(if any)
	 */
	public void draw1MCandles(boolean drawCandleStickChart, boolean drawCurrentDayCandles, boolean drawSignificantChanges, double significantChange){
		String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
		String chartTitle=null;
		CandleSeries T_MINUS_5D_HistoryCandles=instrumentAnalyzer.getT_MINUS_5D_HistoryCandles();
		Map<TimeStamp,Interval<TimeStamp>> exchangeTradingHours_T_MINUS_5=instrumentAnalyzer.getExchangeTradingHours_T_MINUS_5(instrumentAnalyzer.getExchangeCalendar());

		for(Entry<TimeStamp,Interval<TimeStamp>> exchangeTradingHours:exchangeTradingHours_T_MINUS_5.entrySet()){//this goes from T-5 to newest day hours
			TimeStamp day=exchangeTradingHours.getKey();
			chartTitle=symbol+"@"+fmt.format(day);
			Interval<TimeStamp> tradingHours = exchangeTradingHours.getValue();
			CandleSeries dayCandles = T_MINUS_5D_HistoryCandles.getTimeFrame(tradingHours.from, tradingHours.to);
			if(dayCandles.size()==0){
				instrumentAnalyzer.getLog().info("[process1MCandles] no 1m candles for "+chartTitle);
				continue;
			}
			List<Tuple<TimeStamp, Double>> tuples=getClosePriceTuples(dayCandles);
			drawTimeSeriesChart(chartTitle, tuples, drawSignificantChanges, significantChange);
			if(drawCandleStickChart){
				drawCandlestickChart(chartTitle+" candlestick", dayCandles);
			}
//			if(count==(dayRanges-1)){
//				lastFullDay=day;
//			}
//			instrumentAnalyzer.getLog().info(InstrumentAnalyzer.fmt.format(day)+" range:"+InstrumentAnalyzer.fmt.format(tradingHours.from)+"-"+InstrumentAnalyzer.fmt.format(tradingHours.to));
		}
		if(drawCurrentDayCandles){
			TimeStamp now = ema.getTimeStampService().currentTimeStamp();
			TimeStamp openingTimeToday=instrumentAnalyzer.getExchangeCalendar().getOpeningTimeTodayAsTimeStamp(now);
			if(openingTimeToday==null){
				//the exchange won't open today
				return;				
			}else if(now.isAfter(openingTimeToday)){//check if i need some candles from between the open today and now
				//Interval<TimeStamp> todayTradingRangeSoFar = new Interval<TimeStamp>(openingTimeToday,now); 
				CandleSeries candlesOfToday1M= T_MINUS_5D_HistoryCandles.getTimeFrame(openingTimeToday, now);
				if(candlesOfToday1M.size()>2){
					List<Tuple<TimeStamp, Double>> tuples=getClosePriceTuples(candlesOfToday1M);
					chartTitle=symbol+"@"+fmt.format(openingTimeToday);
					drawTimeSeriesChart(chartTitle, tuples, true, 1);
					initTimeseriesCombinedChart(chartTitle);
					return;
				}
			}
		}
	}
	

	double maxDistance = 0d;
	double minDistance = 0d;
			
	AtomicBoolean bollingerBandsInitialized=new AtomicBoolean();
	
	/**
	 * Iterates the associated chartTuples(List&lt;Tuple&lt;TimeStamp,Double&gt;&gt;) and shows/draws a LineSeriesChart.<br/>
	 * If the given drawMinMaxLine(boolean) is true it would also render a line connecting the significant points on the same chart<br/>
	 * Todays date data is charted on another "realtime" chart
	 * @param title
	 * @param tuples
	 * @param drawSignificantChangesLine
	 * @param significantChange
	 */
	public void drawTimeSeriesChart(String title, List<Tuple<TimeStamp,Double>> tuples, boolean drawSignificantChangesLine, double significantChange){
//		CandlestickChart chart = new CandlestickChart();
//		chart.setCandleSeries(T_MINUS_5D_HistoryCandles);
//		chart.setSuppressWeekends();
//		ChartFrame f = new ChartFrame(T_MINUS_5D_HistoryCandles.getSeriesSpecification().getInstrumentSpecification().getSymbol().toString(), chart.getChart());
//		f.setBounds(0,0,2000,500);
//		f.setVisible(true);	
		Chart lineChart = new Chart();
		lineChart.addLineSeriesChart("price", tuples);

		lineChart.setSuppressWeekends();
		ChartFrame chartFrame = new ChartFrame(title, lineChart.getChart());
		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		chartFrame.setBounds(0, 0, (int)dim.getWidth(), (int)dim.getHeight());
		chartFrame.setVisible(true);
	}
	
	public void drawCandlestickChart(String title, CandleSeries candles){
		CandlestickChart chart = new CandlestickChart();
		chart.setCandleSeries(title,candles);
		chart.setSuppressWeekends();
		ChartFrame f = new ChartFrame(title, chart.getChart());
//		f.setBounds(0,0,2000,500);
		Dimension dim = Toolkit.getDefaultToolkit().getScreenSize();
		f.setBounds(0, 0, (int)dim.getWidth(), (int)dim.getHeight());
		f.setVisible(true);		
	}

	

	
	public CandleSeries last200_1DayCandles=null;
	/**
	 * public Candle high200=null;<br/>
	 * Last 200 days highest 1 day historic Candle
	 */
	public Candle high200=null;
	public Candle low200=null;
	public Candle high5=null;
	public Candle low5=null;
	
	public Candle mostRecentHigh=null;
	public Candle mostRecentLow=null;
	public boolean firstRun=true;
	
	CandleSeries maximumsCandleSeries=new CandleSeries();
	CandleSeries minimumsCandleSeries=new CandleSeries();
	long lookBackPeriodNanos = 20*60*1000000000L;//20 minutes
	/**
	 * Call this on every real time Candle to know where are we relative to historic data
	 * @param liveCandle
	 */
	public void updateExtremes(final Candle liveCandle){
		String symbol = instrumentAnalyzer.getInstrumentSpecification().getSymbol().toString();
		if(firstRun){//this happens once
			CandleSeries T_MINUS_1Y_HistoryCandles=instrumentAnalyzer.getT_MINUS_1Y_HistoryCandles();
			last200_1DayCandles=(T_MINUS_1Y_HistoryCandles.size()>200)?(T_MINUS_1Y_HistoryCandles.subList(0, 200)):(T_MINUS_1Y_HistoryCandles);
			high200=last200_1DayCandles.getHigh();
			low200=last200_1DayCandles.getLow();
			high5=instrumentAnalyzer.getT_MINUS_5D_HistoryCandles().getHigh();
			low5=instrumentAnalyzer.getT_MINUS_5D_HistoryCandles().getLow();
			if(!instrumentAnalyzer.getLiveCandleSeriesBag().get().isEmpty()){
				mostRecentHigh = instrumentAnalyzer.getLiveCandleSeriesBag().get().getHigh();
				mostRecentLow= instrumentAnalyzer.getLiveCandleSeriesBag().get().getLow();	
			}else{
				mostRecentHigh=mostRecentLow=liveCandle;
			}	
			if(mostRecentHigh.getHighPrice()>=high5.getHighPrice()){
				high5=mostRecentHigh;
			}
			if(mostRecentLow.getLowPrice()<=low5.getLowPrice()){
				low5=mostRecentLow;
			}
			if(high5.getHighPrice()>=high200.getHighPrice()){
				high200=high5;
			}
			if(low5.getLowPrice()<=low200.getLowPrice()){
				low200=low5;
			}			
		}
		boolean hasNew200DayHigh=false;
		if(liveCandle.getHighPrice()>high200.getHighPrice()){
			hasNew200DayHigh=true;
			high200=liveCandle;//replacing current 200 day high
			instrumentAnalyzer.getLog().info(symbol+" new 200 Day HIGH:"+high200.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(high200.getTimeStamp()));
		}
		boolean hasNew200DayLow=false;
		if(liveCandle.getLowPrice()<low200.getLowPrice()){
			hasNew200DayLow=true;
			low200=liveCandle;
			instrumentAnalyzer.getLog().info(symbol+" new 200 Day LOW:"+low200.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(low200.getTimeStamp()));
		}
		if(liveCandle.getHighPrice()>high5.getHighPrice()){
			high5=liveCandle;
			if(!hasNew200DayHigh){//if there is a new 200 day high its also new 5 day high, don't duplicate messages
				instrumentAnalyzer.getLog().info(symbol+" new 5 Day HIGH:"+high5.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(high5.getTimeStamp()));
			}
		}
		if(liveCandle.getLowPrice()<low5.getLowPrice()){
			low5=liveCandle;
			if(!hasNew200DayLow){//if there is a new 200 day low its also new 5 day low, don't duplicate messages
				instrumentAnalyzer.getLog().info(symbol+" new 5 Day LOW:"+low5.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(low5.getTimeStamp()));
			}
		}
//		if(liveCandle.getHighPrice()> mostRecentHigh.getHighPrice()){
//			mostRecentHigh=liveCandle;
//			instrumentAnalyzer.getLog().info(symbol+" new current session HIGH:"+mostRecentHigh.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentHigh.getTimeStamp()));
//		}
		
//		if(liveCandle.getLowPrice()<mostRecentLow.getLowPrice()){
//			mostRecentLow=liveCandle;
//			instrumentAnalyzer.getLog().info(symbol+" new current session LOW:"+mostRecentLow.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentLow.getTimeStamp()));
//		}
		
		maximumsCandleSeries.add(liveCandle);
		TimeStamp lookBackTimeStamp= new TimeStamp(liveCandle.getTimeStamp().getNanoseconds()-lookBackPeriodNanos);
		if(liveCandle.getHighPrice() == mostRecentHigh.getHighPrice()){
			mostRecentHigh=liveCandle;
		}else if(liveCandle.getHighPrice()> mostRecentHigh.getHighPrice()){
			mostRecentHigh=liveCandle;
//			maximumsCandleSeries=maximumsCandleSeries.getTimeFrame(new TimeStamp(mostRecentHigh.getTimeStamp().getNanoseconds()-1), liveCandle.getTimeStamp());
			maximumsCandleSeries=new CandleSeries();
//			instrumentAnalyzer.getLog().info(symbol+" new current session HIGH:"+mostRecentHigh.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentHigh.getTimeStamp()));
		}else if(mostRecentHigh.getTimeStamp().isBefore(lookBackTimeStamp)){
			maximumsCandleSeries=maximumsCandleSeries.getTimeFrame(new TimeStamp(mostRecentHigh.getTimeStamp().getNanoseconds()+1), liveCandle.getTimeStamp());
			Candle newHigh=maximumsCandleSeries.getHigh();
//			instrumentAnalyzer.getLog().info(symbol+" current session HIGH expired:"+mostRecentHigh.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentHigh.getTimeStamp())+ " new high:" +newHigh.getHighPrice()+"@"+InstrumentAnalyzer.fmt.format(newHigh.getTimeStamp()));
			mostRecentHigh=newHigh;
		}

		minimumsCandleSeries.add(liveCandle);
		if(liveCandle.getLowPrice()== mostRecentLow.getLowPrice()){
			mostRecentLow=liveCandle;
		}else if(liveCandle.getLowPrice()< mostRecentLow.getLowPrice()){
			mostRecentLow=liveCandle;
//			minimumsCandleSeries=minimumsCandleSeries.getTimeFrame(new TimeStamp(mostRecentLow.getTimeStamp().getNanoseconds()-1), liveCandle.getTimeStamp());
			minimumsCandleSeries=new CandleSeries();
//			instrumentAnalyzer.getLog().info(symbol+" new current session LOW:"+mostRecentLow.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentLow.getTimeStamp()));
		}else if(mostRecentLow.getTimeStamp().isBefore(lookBackTimeStamp)){
			minimumsCandleSeries=minimumsCandleSeries.getTimeFrame(new TimeStamp(mostRecentLow.getTimeStamp().getNanoseconds()+1), liveCandle.getTimeStamp());
			Candle newLow=minimumsCandleSeries.getLow();
//			instrumentAnalyzer.getLog().info(symbol+" current session LOW expired:"+mostRecentLow.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(mostRecentLow.getTimeStamp())+ " new low:" +newLow.getLowPrice()+"@"+InstrumentAnalyzer.fmt.format(newLow.getTimeStamp()));
			mostRecentLow=newLow;
		}		
	
		if(!firstRun)
			return;		
		firstRun=false;
	}
	/**
	 * Merges all historical Candles and recent real time Candles
	 * @param realTimeCandles
	 * @param last5Days
	 * @param oneYearHistory
	 */
	public void mergeCandles(final CandleSeries realTimeCandles, final CandleSeries last5Days, final CandleSeries oneYearHistory){
		if(realTimeCandles!=null){
			allCandlesMerged=TimeSeriesUtils.merge(last5Days, realTimeCandles, oneYearHistory);
		}else{
			allCandlesMerged=TimeSeriesUtils.merge(last5Days, oneYearHistory);
		}
	}

	/**
	 * returns a recommendation number between 1(strong buy), 0(neutral) and -1(strong sell) by analyzing the given candles(CandleSeries)
	 * @param candles
	 * @param lookBackPeriodNanos
	 * @param significantChange
	 * @param noise
	 * @return
	 */
	public double getRecomendation(CandleSeries candles, double significantChange, double noise){
//		if(true) return 0;//lets test mkt scanners
		if(candles.size()==0) return 0;
		Candle liveCandle=candles.get(0);
		if(low5==liveCandle){//this just became the new 5 day low
			return 1d;//strong buy
		}else if(high5==liveCandle){//this just became the new 5 day high
			return -1d;//strong sell
		}
		
		if(candles.size()<5) return 0;
		Candle prevLiveCandle=candles.get(1);
		TimeStamp liveCandleTimeStamp=liveCandle.getTimeStamp();
		
//		TimeStamp lookBackTimeStamp= new TimeStamp(liveCandleTimeStamp.getNanoseconds()-lookBackPeriodNanos);
//		CandleSeries observedCandleSeries= candles.getTimeFrame(lookBackTimeStamp, liveCandleTimeStamp);
		Candle highCandle=mostRecentHigh;//observedCandleSeries.getHigh();
		Candle lowCandle= mostRecentLow;//observedCandleSeries.getLow();
//		String to=instrumentAnalyzer.fmt.format(liveCandleTimeStamp);
//		String from=instrumentAnalyzer.fmt.format(lookBackTimeStamp);
//		String high=instrumentAnalyzer.fmt.format(highCandle.getTimeStamp());
//		String low=instrumentAnalyzer.fmt.format(lowCandle.getTimeStamp());
		
		double prevdownshift=highCandle.getClosePrice()-prevLiveCandle.getClosePrice();
		double prevupshift=prevLiveCandle.getClosePrice()-lowCandle.getClosePrice();
		
		double downshift=highCandle.getClosePrice()-liveCandle.getClosePrice();
		double upshift=liveCandle.getClosePrice()-lowCandle.getClosePrice();
		
		if(prevdownshift<significantChange && prevupshift<significantChange) 
		{return 0d;}
		
		if(downshift<significantChange && upshift<significantChange) 
		{return 0d;}
		
		if(lowCandle.getTimeStamp().isAfter(highCandle.getTimeStamp()) && prevdownshift>=significantChange&&(downshift>(significantChange*noise))&&prevdownshift>=downshift)
		{
			return 1d;//strong buy
		}
		
		if(highCandle.getTimeStamp().isAfter(lowCandle.getTimeStamp()) && prevupshift>=significantChange&&(upshift>(significantChange*noise))&&prevupshift>=upshift)
		{
			return -1d;//strong sell
		}
//		if(highCandle.getClosePrice()-)
		return 0d;
	}
	
	/**
	 * If I haven't already decided to buy and conditions are right, buy
	 */
	public void actUponRecommendation(double recommendation){
		BracketPositionService.Tracker positionTracker=(BracketPositionService.Tracker)instrumentAnalyzer.getPositionTracker();
		Logger log=instrumentAnalyzer.getLog();
		InstrumentSpecification instrumentSpecification=instrumentAnalyzer.getInstrumentSpecification();
		if(positionTracker!=null && !positionTracker.isClosed()){
			if(positionTracker.hasMainOrderReachedMaximumInactivePeriod()){
				IOrderTracker mainOrderTracker=positionTracker.getMainOrderTracker();
				if(mainOrderTracker!=null){
					log.info("[actUponRecommendation] cancelling main order");
					mainOrderTracker.cancel();
				}
				//price moved away from the order, cancel? position could be partially opened as well
				if(positionTracker.getMainOrderFilledQuantity()!=0){
					//cancel main order
					//to do
					//partially executed
					log.info("[actUponRecommendation] placing bracket orders");
					positionTracker.placeBracketOrders();
					return;
				}else if(!positionTracker.isClosed()){
					//nothing executed at all
					log.info("[actUponRecommendation] close position");
					positionTracker.close();
				}
			}else if(positionTracker.hasLimitOrderReachedMaximumInactivePeriod()){
				IOrderTracker limitOrderTracker=positionTracker.getLimitOrderTracker();
				if(limitOrderTracker!=null){
					log.info("[actUponRecommendation] updating limit order because it reached max inactive period");
					double limitOrderAveragePrice=positionTracker.getLimitOrderAveragePrice();
					double limitOrderFilledQuantity=positionTracker.getLimitOrderFilledQuantity();
					double limitOrderCommission=positionTracker.getLimitOrderCommission();
					
					double mainOrderAveragePrice=positionTracker.getMainOrderAveragePrice();
					double mainOrderFilledQuantity=positionTracker.getMainOrderFilledQuantity();
					double mainOrderCommission=positionTracker.getMainOrderCommission();
					
					double stopOrderAveragePrice=positionTracker.getStopOrderAveragePrice();
					double stopOrderFilledQuantity=positionTracker.getStopOrderFilledQuantity();
					double stopOrderCommission=positionTracker.getStopOrderCommission();
					
					double outstandingBalance=(mainOrderAveragePrice*mainOrderFilledQuantity)+mainOrderCommission+limitOrderCommission+stopOrderCommission-(limitOrderAveragePrice*limitOrderFilledQuantity)-(stopOrderAveragePrice*stopOrderFilledQuantity);
					double outstandingQuantity=mainOrderFilledQuantity-stopOrderFilledQuantity-limitOrderFilledQuantity;
					double price=outstandingBalance/outstandingQuantity;
					Order updateLimitOrder=new Order(limitOrderTracker.getOrder());
					updateLimitOrder.setId(null);
					BigDecimal bd = new BigDecimal(price);
					if(updateLimitOrder.getOrderSide()==OrderSide.BUY){
						bd = bd.setScale(2,BigDecimal.ROUND_DOWN);
					}else{
						bd = bd.setScale(2,BigDecimal.ROUND_UP);
					}
					price=bd.doubleValue();
					updateLimitOrder.setLimitPrice(price);
					updateLimitOrder.setQuantity(outstandingQuantity);
					limitOrderTracker.update(updateLimitOrder);
					//limitOrderTracker.cancel(); update closer to current price but not get into a loss
				}
			}
//			else if(positionTracker.hasStopOrderReachedMaximumInactivePeriod()){
//				IOrderTracker stopOrderTracker=positionTracker.getStopOrderTracker();
//				if(stopOrderTracker!=null){
//					//stopOrderTracker.cancel(); update closer to current price but not get into a loss
//				}
//			}
			if(!positionTracker.isClosed()){
				return;
			}
		}
		
		
		if(recommendation==0){
			return;
		}
		
		double MAX_LONG_QUANTITY_PER_POSITION=200;
		double MAX_SHORT_QUANTITY_PER_POSITION=-200;
		//get current position
		if(ema.getBroker() instanceof IBPortfolioManagingBroker){
			IBPortfolioManagingBroker broker =(IBPortfolioManagingBroker)ema.getBroker();
			Portfolio portfolio=broker.getBrokerAccount().getPortfolio();
			IBPosition position=portfolio.hasPosition(instrumentSpecification) ? (IBPosition)portfolio.getPosition(instrumentSpecification) : null;
			if(position!=null){
				double currentQuantity=position.getQuantity();
				if(recommendation==1&&currentQuantity>=MAX_LONG_QUANTITY_PER_POSITION){
					return;
				}else if(recommendation==-1&&currentQuantity<=MAX_SHORT_QUANTITY_PER_POSITION){
					return;
				}
			}
		}
		AtomicReference<QuoteSeries> liveQuoteSeriesBag = instrumentAnalyzer.getLiveQuoteSeriesBag();
		AtomicReference<CandleSeries> liveCandleSeriesBag = instrumentAnalyzer.getLiveCandleSeriesBag();
		AtomicReference<TradeIndicationSeries> liveTradeIndicationSeriesBag = instrumentAnalyzer.getLiveTradeIndicationSeriesBag();
		if(liveCandleSeriesBag==null||liveCandleSeriesBag.get()==null||liveCandleSeriesBag.get().size()==0){
			log.info("waiting for live Candle");
			return;
		}
		//MarketDepthSeries marketDepthSeries = null;
//		if(liveMarketDepthSeriesBag==null){
//			log.info("waiting for live MarketDepth");
//			return;
//		}else{
//			//marketDepthSeries = liveMarketDepthSeriesBag.get();
//			//if(marketDepthSeries==null||marketDepthSeries.size()==0){
//			//	log.info("waiting for live MarketDepth");
//			//	return;
//			//}
//		}
		if(liveQuoteSeriesBag==null||liveQuoteSeriesBag.get()==null||liveQuoteSeriesBag.get().size()==0){
			log.info("waiting for live Quote");
			return;
		}
		
		if(liveTradeIndicationSeriesBag==null||liveTradeIndicationSeriesBag.get()==null||liveTradeIndicationSeriesBag.get().size()==0){
			log.info("waiting for live TradeIndication");
			return;
		}
		
		Order order = new Order();

		TimeStamp currentTimeStamp=ema.getTimeStampService().currentTimeStamp();
		IExchangeCalendar exchangeCalendar = instrumentAnalyzer.getExchangeCalendar();
		Calendar calendar=GregorianCalendar.getInstance(exchangeCalendar.getTimeZone());
		calendar.setTimeInMillis(currentTimeStamp.getDate().getTime());
		if(calendar.get(Calendar.HOUR_OF_DAY)>=16){//don't submit new DAY orders(default) after 16:00, existing ones can work till 16:30 and then they get canceled by the exchange
			return;
		}
		order.setInstrumentSpecification(instrumentSpecification);
		order.setOrderTimeStamp(currentTimeStamp);
		if(recommendation==1){
			order.setOrderSide(OrderSide.BUY);
		}else if(recommendation==-1){
			order.setOrderSide(OrderSide.SELL);
		}
//		order.setOrderType(OrderType.LIMIT);
		order.setOrderType(OrderType.MARKET_TO_LIMIT);
		Candle latestCandle=liveCandleSeriesBag.get().get(0);
		Quote latestQuote=liveQuoteSeriesBag.get().get(0);
		TradeIndication latestTradeIndication=liveTradeIndicationSeriesBag.get().get(0);
		double askPrice=FinancialLibrary.minOf(latestCandle.getClosePrice(), latestQuote.getAskPrice(), latestTradeIndication.getPrice());
		double optimalQuantity = CommissionsCalculator.determineOrderQuantityForOptimalMinimumCommission(order);
		order.setQuantity(optimalQuantity);
		//double approximateCommission = CommissionsCalculator.determineApproximateCommissionForOrder(buyOrder);
		//log.info("approximateCommission="+approximateCommission+" quantity="+optimalQuantity);
		order.setLimitPrice(askPrice);
		instrumentAnalyzer.setPositionTracker(instrumentAnalyzer.getPositionService().openPosition(order));
//		hasRequestedToGoLong.set(true);
	}
}
